#ifndef SHAREDMEM_H_
#define SHAREDMEM_H_

#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "../common/Exception.h"

namespace ipc
{

#define SHM_DONT_CARE 0
#define SHM_PERMS 0666

template <class T>
class SharedMemory
{

private:
	int m_id;
	int m_size;
    T*  m_data;

	/**
	 * Constructor (privado)
	 * @param id   ID de la shmem
	 * @param size Tamaño de la shmem
	 */
    SharedMemory( int id, int size );

public:

	/**
	 * Construtor por defecto
	 */
    SharedMemory();

	/**
	 * Destructor
	 */
    ~SharedMemory();

	/**
	 * Inicializador
	 *
	 * @warning Solo puede utilizarce si se paso el tamaño como parametro en
	 *           el get (o si se uso el create)
	 *
	 * @param data Valor con el cual inicializar el array
	 */
    void initialize( T data );

	/**
	 * Escribe el valor en la posicion indicada
	 *
	 * @param value Valor a escribir
	 * @param index Posicion del array a escribir
	 */
    void write( T value, int index = 0 );

	/**
	 * Lee el valor de la posicion dada
	 *
	 * @param index Posicion del array a leer
	 *
	 * @return El valor leido
	 */
    T read( int index = 0 );

	/**
     * Metodo para crear una Shared Memory
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     * @param semAmmount Cantidad de elementos en el array
     * @param flags flags del array (no tocar)
     *
     * @return La Shared Memory creada
     */
    static SharedMemory<T> create( std::string file, int charKey, int size,
            int flags = IPC_CREAT | IPC_EXCL );

    /**
     * Metodo para crear no exclusivo una Shared Memory
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     * @param semAmmount Cantidad de elementos en el array
     *
     * @return La Message Queue creada
     */
    static SharedMemory<T> createNonExclusive( std::string file, int charKey, int size);

    /**
     * Metodo para obtener una Shared Memory (ya creada)
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     *
     * @return La Shared Memory
     */
    static SharedMemory<T> get( std::string file, int charKey, int size =
            SHM_DONT_CARE );

    /**
     * Metodo para destruir una Shared Memory
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     */
    static void destroy( std::string file, int charKey );

    /**
     * Metodo para destruir una Shared Memory, si no existe no tiene efecto
     *
     * @param file Archivo base
     * @param charKey Caracter para usar como clave
     */
    static void destroyNonExclusive( std::string file, int charKey );
};

template<class T>
inline SharedMemory<T>::SharedMemory() : m_id( -1 ), m_size( 0 ), m_data( NULL ) {}

template<class T>
inline SharedMemory<T>::SharedMemory( int id, int size ) :
        m_id( id ), m_size( size )
{
    std::string error;

    // Attach del bloque de memoria al espacio de direcciones del proceso
    this->m_data = (T*) shmat( this->m_id, NULL, 0 );

    if ( this->m_data < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error attaching the shared memory: " + error );
    }
}

template<class T>
inline SharedMemory<T>::~SharedMemory()
{
}

template<class T>
inline void SharedMemory<T>::initialize( T data )
{
    for ( int i = 0; i < m_size; i++ )
    {
        this->m_data[i] = data;
    }
}

template<class T>
inline void SharedMemory<T>::write( T value, int index )
{
    this->m_data[index] = value;
}

template<class T>
inline T SharedMemory<T>::read( int index )
{
    return this->m_data[index];
}

template<class T>
inline SharedMemory<T> SharedMemory<T>::create( std::string file, int charKey,
        int size, int flags )
{
    std::string touchCmd;
    std::string error;
    key_t key;
    int id;

    touchCmd = "touch " + file;
    system( touchCmd.c_str() );

    key = ftok( file.c_str(), charKey );

    if ( key < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error while creating the shared memory key on "
                "file:" + file + ". Error: " + error );
    }
    else
    {
        flags |= SHM_PERMS;
        id = shmget( key, sizeof(T) * size, flags );

        if ( id < 0 )
        {
            error = strerror( errno );
            throw Exception( "Error creating the shared memory: " + error );
        }
    }
    return SharedMemory<T>( id, size );
}


template<class T>
inline SharedMemory<T> SharedMemory<T>::createNonExclusive( std::string file, int charKey, int size) {
    return SharedMemory<T>::create( file, charKey, size, IPC_CREAT );
}

template<class T>
inline SharedMemory<T> SharedMemory<T>::get( std::string file, int charKey,
        int size )
{
    return SharedMemory<T>::create( file, charKey, size, IPC_EXCL );
}

template<class T>
inline void SharedMemory<T>::destroy( std::string file, int charKey )
{
    std::string error;

    SharedMemory<T> shm = SharedMemory<T>::get( file, charKey );

    if ( shmctl( shm.m_id, IPC_RMID, NULL ) < 0 )
    {
        error = strerror( errno );
        throw Exception( "Error destroying the shared memory: " + error );
    }
}

template<class T>
inline void SharedMemory<T>::destroyNonExclusive( std::string file, int charKey )
{
    try {
    	SharedMemory<T> shm = SharedMemory<T>::get( file, charKey );
    	shmctl( shm.m_id, IPC_RMID, NULL );
    } catch(...) {};
}


} /* namespace ipc */
#endif // SHAREDMEM_H_
